package com.jt.resource.aspect;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jt.resource.annotation.RequiredLog;
import com.jt.resource.pojo.Log;
import com.jt.resource.service.RemoteLogService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;

@Slf4j
@Aspect
@Component
public class LogAspect {
    /**
     * 注解方式的切入点表达式定义
     */
    @Pointcut("@annotation(com.jt.resource.annotation.RequiredLog)")
    public void doLog(){
        //方法中不写内容，只负责承载切入点表达式的定义
    }

    /**
     * Around 通知，此通知中可以手动调用目标切入点方法。我们可以在目标切入点
     * 方法执行之前，之后做一些业务拓展，例如获取并记录相关日志信息。
     * @param joinPoint 连接点(封装了要执行的目标切入点方法信息)
     * @return 目标切入点方法的返回值
     * @throws Throwable
     */
    @Around("doLog()")
    public Object doInvoke(ProceedingJoinPoint joinPoint)throws Throwable{
        long time=0;
        int status=1;//ok
        String error="";
        long t1=System.currentTimeMillis();
        log.debug("Before {}",t1);
        try {
            Object result = joinPoint.proceed();//执行目标执行链(其它通知方法，目标方法)
            long t2 = System.currentTimeMillis();
            time=t2-t1;
            log.debug("After {}", t2);
            return result;
        }catch (Throwable e){
            long t3 = System.currentTimeMillis();
            time=t3-t1;
            log.debug("Exception {}", t3);
            status=0;
            error=e.getMessage();
            throw e;
        }finally{
            log.debug("execute method time :{}",time);
            saveLog(joinPoint,status,error,time);
        }
    }
    //保存日志(将获取到的日志封装到Log对象，然后传递给system工程)
    private void saveLog(ProceedingJoinPoint joinPoint,
                         int status,String error,long time) throws NoSuchMethodException, JsonProcessingException {
        //1.获取用户行为日志
        //1.1获取请求ip地址(在非Controller对象中可以借助RequestContextHolder对象进行request对象获取)
        ServletRequestAttributes requestAttributes =(ServletRequestAttributes)
                RequestContextHolder.getRequestAttributes();
        HttpServletRequest request=requestAttributes.getRequest();
        String ip=request.getRemoteAddr();
        //1.2获取登录用户名(SecurityContextHolder存储了jwt令牌中信息)
        String username=(String)SecurityContextHolder.getContext()
                .getAuthentication().getPrincipal();
        //1.3获取操作名(RequiredLog中的value属性的值)
        Class<?> targetCls = joinPoint.getTarget().getClass();
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        Method targetMethod=
        targetCls.getDeclaredMethod(signature.getName(),signature.getParameterTypes());
        RequiredLog requiredLog =
                targetMethod.getAnnotation(RequiredLog.class);
        String operation=requiredLog.value();
        //1.4获取目标方法名
        String targetMethodName=targetCls.getName()+"."+targetMethod.getName();
        //1.5获取目标方法实际参数
        String params=new ObjectMapper().writeValueAsString(joinPoint.getArgs());
        //2.封装用户行为日志
        Log userLog=new Log();
        userLog.setIp(ip);
        userLog.setUsername(username);
        userLog.setCreatedTime(new Date());
        userLog.setOperation(operation);
        userLog.setMethod(targetMethodName);
        userLog.setParams(params);
        userLog.setTime(time);
        userLog.setStatus(status);
        userLog.setError(error);
        //3.将用户行为日志传递个System工程
        log.debug("user.log: {}",userLog);
        remoteLogService.insertLog(userLog);
    }
    @Autowired
    private RemoteLogService remoteLogService;
}
